package com.estate.report.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.estate.common.annotation.DataScope;
import com.estate.common.constant.HttpStatus;
import com.estate.common.core.domain.BaseEntity;
import com.estate.common.core.domain.entity.SysUser;
import com.estate.common.exception.ServiceException;
import com.estate.common.utils.DateUtils;
import com.estate.common.utils.StringUtils;
import com.estate.report.domain.DataStatistics;
import com.estate.report.domain.RateSourceDataBo;
import com.estate.report.domain.dto.DataStatisticsDto;
import com.estate.report.domain.dto.DataStatisticsListDto;
import com.estate.report.domain.dto.PerformanceStatisticsDto;
import com.estate.report.domain.vo.*;
import com.estate.report.enums.DataStatisticsTypeEnum;
import com.estate.report.enums.StatisticsSearchTypeEnum;
import com.estate.report.mapper.DataStatisticsMapper;
import com.estate.report.service.IDataStatisticsService;
import com.estate.system.service.ISysUserService;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@Slf4j
public class DataStatisticsServiceImpl extends ServiceImpl<DataStatisticsMapper, DataStatistics> implements IDataStatisticsService {

    @Resource
    ISysUserService userService;

    @Resource
    ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Resource
    IDataStatisticsService dataStatisticsService;

    @Override
    public void genStatisticsData(Date startTime, Date endTime) {
        // 处理参数
        if (Objects.isNull(endTime)) {
            endTime = DateUtils.getNowDate();
        }
        JSONObject params = new JSONObject();
        if (Objects.nonNull(startTime)) {
            params.put("startTime", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, startTime));
        }
        params.put("endTime", DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, endTime));

        int total = 0;
        CompletionService<List<DataStatistics>> completionService = new ExecutorCompletionService<>(threadPoolTaskExecutor);
        for (DataStatisticsTypeEnum value : DataStatisticsTypeEnum.values()) {
            if (StringUtils.isNotEmpty(value.getSql())) {
                try {
                    total++;
                    completionService.submit(() -> baseMapper.executeSql(StringUtils.replacePlaceholdersWithDefault(value.getSql(), params)));
                } catch (Exception e) {
                    log.info("统计sql异常, sql: {}", value.getSql());
                    log.error("统计sql异常", e);
                }
            }
        }
        for (int i = 0; i < total; i++) {
            try {
                List<DataStatistics> dataStatistics = completionService.take().get();
                if (!dataStatistics.isEmpty()) {
                    baseMapper.saveBatch(dataStatistics);
                }
            } catch (Exception e) {
                log.error("统计数据异常, e", e);
            }
        }
    }

    @Override
    public Object getStatisticsData(String type) {
        StatisticsSearchTypeEnum typeEnum = StatisticsSearchTypeEnum.getByType(type);
        if (Objects.isNull(typeEnum)) {
            throw new ServiceException("Statistical type does not exist", HttpStatus.BAD_REQUEST);
        }
        Date weekStart = DateUtils.getWeekStart();
        Date weekEnd = DateUtils.getWeekEnd();
        switch (typeEnum) {
            case SALE_GMV:
            case LEASE_GMV:
                return dataStatisticsService.getGMV(new BaseEntity(), typeEnum, weekStart, weekEnd);
            case SALE_NUM:
            case LEASE_NUM:
            case SALE_NEW_LEADS:
            case LEASE_NEW_LEADS:
                return dataStatisticsService.getNum(new BaseEntity(), typeEnum, weekStart, weekEnd);
            case SALES_MID_PRICE:
            case LEASE_MID_PRICE:
                return dataStatisticsService.getMidPrice(new BaseEntity(), typeEnum, weekStart, weekEnd);
            case SALE_RANK:
            case LEASE_RANK:
                return dataStatisticsService.getRankList(new BaseEntity(), typeEnum, weekStart, weekEnd);
            case SALE_GMV_TREND:
            case LEASE_GMV_TREND:
                return dataStatisticsService.getTrendList(new BaseEntity(), typeEnum, weekStart, weekEnd);
            case SALE_TYPE_RATE:
            case LEASE_TYPE_RATE:
            case SALE_PRICE_RANGE_RATE:
            case LEASE_PRICE_RANGE_RATE:
            case SALE_SUBURB_RATE:
            case LEASE_SUBURB_RATE:
                return dataStatisticsService.getRateList(new BaseEntity(), typeEnum, weekStart, weekEnd);
            default:
                return null;
        }
    }

    @Override
    @DataScope(deptAlias = "d", teamAlias = "t")
    public SummaryStatisticsVo getGMV(BaseEntity dataScope, StatisticsSearchTypeEnum typeEnum, Date weekStart, Date weekEnd) {
        BigDecimal thisWeekSum = baseMapper.getSum(
                DataStatisticsDto.builder()
                        .type(typeEnum.getStatisticsType())
                        .startTime(weekStart)
                        .endTime(weekEnd)
                        .field("(data ->> 'price')::numeric")
                        .build(),
                dataScope
        );
        BigDecimal lastWeekSum = baseMapper.getSum(
                DataStatisticsDto.builder()
                        .type(typeEnum.getStatisticsType())
                        .startTime(DateUtils.addDays(weekStart, -7))
                        .endTime(DateUtils.addDays(weekStart, -1))
                        .field("(data ->> 'price')::numeric")
                        .build(),
                dataScope
        );
        return SummaryStatisticsVo.builder()
                .data(thisWeekSum)
                .comparableRate(getRate(thisWeekSum, lastWeekSum))
                .build();
    }

    @Override
    @DataScope(deptAlias = "d", teamAlias = "t")
    public SummaryStatisticsVo getNum(BaseEntity dataScope, StatisticsSearchTypeEnum typeEnum, Date weekStart, Date weekEnd) {
        Integer thisWeekNum = baseMapper.getCount(
                DataStatisticsDto.builder()
                        .type(typeEnum.getStatisticsType())
                        .startTime(weekStart)
                        .endTime(weekEnd)
                        .build(),
                dataScope
        );
        Integer lastWeekNum = baseMapper.getCount(
                DataStatisticsDto.builder()
                        .type(typeEnum.getStatisticsType())
                        .startTime(DateUtils.addDays(weekStart, -7))
                        .endTime(DateUtils.addDays(weekStart, -1))
                        .build(),
                dataScope
        );
        return SummaryStatisticsVo.builder()
                .data(thisWeekNum)
                .comparableRate(getRate(BigDecimal.valueOf(thisWeekNum), BigDecimal.valueOf(lastWeekNum)))
                .build();
    }

    /**
     * 查询中位数
     */
    @Override
    @DataScope(deptAlias = "d", teamAlias = "t")
    public SummaryStatisticsVo getMidPrice(BaseEntity dataScope, StatisticsSearchTypeEnum typeEnum, Date weekStart, Date weekEnd) {
        // 本周中位数
        BigDecimal thisWeekMidPrice = BigDecimal.ZERO;
        List<BigDecimal> priceList = baseMapper.getPriceList(
                DataStatisticsDto.builder()
                        .type(typeEnum.getStatisticsType())
                        .startTime(weekStart)
                        .endTime(weekEnd)
                        .build(),
                dataScope
        );
        if (!priceList.isEmpty()) {
            if (priceList.size() % 2 == 0) {
                thisWeekMidPrice = priceList.get(priceList.size() / 2).add(priceList.get(priceList.size() / 2 - 1)).divide(new BigDecimal(2), 2, RoundingMode.HALF_UP);
            } else {
                thisWeekMidPrice = priceList.get(priceList.size() / 2);
            }
        }
        // 上周中位数
        BigDecimal lastWeekMidPrice = BigDecimal.ZERO;
        priceList = baseMapper.getPriceList(
                DataStatisticsDto.builder()
                        .type(typeEnum.getStatisticsType())
                        .startTime(DateUtils.addDays(weekStart, -7))
                        .endTime(DateUtils.addDays(weekStart, -1))
                        .build(),
                dataScope
        );
        if (!priceList.isEmpty()) {
            if (priceList.size() % 2 == 0) {
                lastWeekMidPrice = priceList.get(priceList.size() / 2).add(priceList.get(priceList.size() / 2 - 1)).divide(new BigDecimal(2), 2, RoundingMode.HALF_UP);
            } else {
                lastWeekMidPrice = priceList.get(priceList.size() / 2);
            }
        }
        return SummaryStatisticsVo.builder()
                .data(thisWeekMidPrice)
                .comparableRate(getRate(thisWeekMidPrice, lastWeekMidPrice))
                .build();
    }

    @Override
    @DataScope(deptAlias = "d", teamAlias = "t")
    public List<RankStatisticsVo> getRankList(BaseEntity dataScope, StatisticsSearchTypeEnum typeEnum, Date weekStart, Date weekEnd) {
        List<JSONObject> list = baseMapper.getDataList(
                DataStatisticsListDto.builder()
                        .type(typeEnum.getStatisticsType())
                        .startTime(weekStart)
                        .endTime(weekEnd)
                        .select("case when sum((data ->> 'price')::numeric) is null then 0 else sum((data ->> 'price')::numeric) end as data, " +
                                "user_id, first_name, last_name")
                        .groupBy("user_id, first_name, last_name")
                        .orderBy("data desc")
                        .limit(5)
                        .build(),
                dataScope
        );
        return list.stream()
                .map(item -> RankStatisticsVo.builder()
                        .userId(item.getLong("user_id"))
                        .firstName(item.getString("first_name"))
                        .lastName(item.getString("last_name"))
                        .data(item.getBigDecimal("data"))
                        .build()
                )
                .collect(Collectors.toList());
    }

    @Override
    @DataScope(deptAlias = "d", teamAlias = "t")
    public List<TrendStatisticsVo> getTrendList(BaseEntity dataScope, StatisticsSearchTypeEnum typeEnum, Date weekStart, Date weekEnd) {
        List<JSONObject> list = baseMapper.getDataList(
                DataStatisticsListDto.builder()
                        .type(typeEnum.getStatisticsType())
                        .startTime(weekStart)
                        .endTime(weekEnd)
                        .select("case when sum((data ->> 'price')::numeric) is null then 0 else sum((data ->> " +
                                "'price')::numeric) end as data, time")
                        .groupBy("time")
                        .build(),
                dataScope
        );
        Map<String, JSONObject> map = list.stream()
                .collect(
                        Collectors.toMap(
                                item -> DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, item.getDate("time")),
                                Function.identity(),
                                (key1, key2) -> key1)
                );

        // 根据weekStart 和 weekEnd 生成每个日期
        List<Date> dateList = Lists.newArrayList(weekStart);
        Date date = weekStart;
        while(date.before(weekEnd)) {
            date = DateUtils.addDays(date, 1);
            dateList.add(date);
        }
        return dateList.stream()
                .map(item -> {
                    TrendStatisticsVo.TrendStatisticsVoBuilder builder = TrendStatisticsVo.builder().date(item);
                    String dateStr = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, item);
                    if (map.containsKey(dateStr)) {
                        builder.data(map.get(dateStr).getBigDecimal("data"));
                    } else {
                        builder.data(BigDecimal.ZERO);
                    }
                    return builder.build();
                })
                .collect(Collectors.toList());
    }

    @Override
    @DataScope(deptAlias = "d", teamAlias = "t")
    public List<RateStatisticsVo> getRateList(BaseEntity dataScope, StatisticsSearchTypeEnum typeEnum, Date weekStart, Date weekEnd) {
        List<RateSourceDataBo> sourceList = baseMapper.getRateSourceList(
                        DataStatisticsListDto.builder()
                                .type(typeEnum.getStatisticsType())
                                .startTime(weekStart)
                                .endTime(weekEnd)
                                .build(),
                        dataScope
                )
                .stream()
                .filter(item -> Objects.nonNull(item) && Objects.nonNull(item.getValue()))
                .collect(Collectors.toList());
        List<RateStatisticsVo> result = Lists.newArrayList();
        sourceList.stream()
                .map(RateSourceDataBo::getValue)
                // 对value求和
                .reduce(BigDecimal::add)
                .ifPresent(sum -> {
                    sourceList.forEach(item -> {
                        RateStatisticsVo.RateStatisticsVoBuilder builder = RateStatisticsVo.builder()
                                .name(item.getKey())
                                .data(item.getValue())
                                .rate(item.getValue().divide(sum, 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100)).doubleValue());
                        result.add(builder.build());
                    });
                });
        return result;
    }

    @Override
    @DataScope(deptAlias = "d", teamAlias = "t")
    public List<PerformanceVo> getPerformanceList(BaseEntity dataScope, PerformanceStatisticsDto dto) {
        return baseMapper.getPerformanceData(dto, dataScope);
    }

    @Override
    @DataScope(deptAlias = "d", teamAlias = "t")
    public PerformanceDetailVo getPerformanceDetail(BaseEntity dataScope, PerformanceStatisticsDto dto) {
        List<PerformanceVo> thisWeekPerformances = baseMapper.getPerformanceData(dto, dataScope);
        SysUser sysUser = userService.selectUserById(dto.getUserId());
        if (thisWeekPerformances.isEmpty() || Objects.isNull(sysUser)) {
            return null;
        }
        PerformanceVo thisWeekPerformance = thisWeekPerformances.get(0);

        dto.setStartTime(DateUtils.addDays(dto.getStartTime(), -7));
        dto.setEndTime(DateUtils.addDays(dto.getEndTime(), -7));
        List<PerformanceVo> lastWeekPerformances = baseMapper.getPerformanceData(dto, dataScope);

        // 计算环比
        double transactionRatio = 100;
        double gmvRatio = 100;
        if (!lastWeekPerformances.isEmpty()) {
            PerformanceVo lastWeekPerformance = lastWeekPerformances.get(0);
            transactionRatio = getRate(thisWeekPerformance.getTransactions(), lastWeekPerformance.getTransactions());
            gmvRatio = getRate(thisWeekPerformance.getGmv(), lastWeekPerformance.getGmv());
        }

        // 查询团队领导
        String teamLeaderName = "";
        if (Objects.nonNull(sysUser.getTeamId())) {
            SysUser userDto = new SysUser();
            userDto.setTeamId(sysUser.getTeamId());
            List<SysUser> teamUserList = userService.selectUserList(userDto);
            teamLeaderName = teamUserList.stream()
                    .filter(user -> StringUtils.isNotEmpty(user.getRoleTag()) && user.getRoleTag().contains("team_leader"))
                    .map(user -> {
                        String name = "";
                        if (StringUtils.isNotEmpty(user.getFirstName())) {
                            name = user.getFirstName();
                        }
                        if (StringUtils.isNotEmpty(user.getLastName())) {
                            name += " " + user.getLastName();
                        }
                        return name;
                    })
                    .collect(Collectors.joining(","));
        }
        return PerformanceDetailVo.builder()
                .userId(dto.getUserId())
                .salesName(thisWeekPerformance.getSalesName())
                .groupName(thisWeekPerformance.getGroupName())
                .teamleaderName(teamLeaderName)
                .transactionRatio(transactionRatio)
                .gmvRatio(gmvRatio)
                .avatarUrl(sysUser.getAvatar())
                .build();
    }

    private double getRate(BigDecimal now, BigDecimal before) {
        if(before.compareTo(BigDecimal.ZERO) == 0) {
            if(now.compareTo(BigDecimal.ZERO) > 0) {
                return 100;
            } else {
                return 0;
            }
        }
        BigDecimal diff = now.subtract(before);
        return diff.divide(before, 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100)).doubleValue();
    }

}
